perm filename INITEX.CH[TEX,DEK]5 blob sn#696994 filedate 1983-01-24 generic text, type C, neo UTF8
COMMENT ⊗   VALID 00050 PAGES
C REC  PAGE   DESCRIPTION
C00001 00001
C00007 00002	\def\botofcontents{\vskip 0pt plus 1fil minus 1.5in}
C00008 00003	The |banner| string defined here should be changed whenever \TeX\
C00009 00004	@ Some of the code below is intended to be used only when diagnosing the
C00011 00005	@ This program has two important variations: (1) There is a long and slow
C00013 00006	@<Compiler directives@>=
C00014 00007	@ The following parameters can be changed at compile time to extend or
C00018 00008	One can't simply make helter-skelter changes to the following constants,
C00020 00009	@ The ascii code is ``standard'' only to a certain extent, since many
C00022 00010	@p function a_open_in(var f:alpha_file):boolean
C00025 00011	@ Input from text files is read one line at a time, using a routine called
C00028 00012	@p function input_ln(var f:alpha_file):boolean {inputs the next line
C00030 00013	@ The user's terminal acts essentially like other files of text, except
C00031 00014	@ Here is how to open the terminal files
C00032 00015	@ The following program does the required initialization
C00037 00016	@<Character |k| cannot be printed@>=
C00038 00017	@p procedure term_input {gets a line from the terminal}
C00039 00018	@ It is desirable to provide an `\.E' option here that gives the user
C00042 00019	It is usually most efficient to have |min_quarterword=min_halfword=0|,
C00043 00020	The inner loop of \TeX\ will run faster with respect to compilers
C00045 00021	cat_code(carriage_return)←car_ret cat_code(" ")←spacer
C00047 00022	@ The following procedure, which is called just before \TeX\ initializes its
C00048 00023	The global variable |line| contains the line number in the topmost
C00050 00024	@<Print location of current line@>=
C00051 00025	@ The |begin_file_reading| procedure starts a new level of input for lines
C00053 00026	@ Conversely, the variables must be downdated when such a level of input
C00054 00027	@p procedure check_outer_validity
C00056 00028	@ If the user has set the |pausing| parameter to some nonzero value,
C00058 00029	@ The first line of a file must be treated specially, since |input_ln|
C00060 00030	@ When we skip conditional text, we keep track of the line number
C00061 00031	@p procedure pass_text
C00062 00032	@ The file names we shall deal with have the following structure:
C00064 00033	@ Input files that can't be found in the user's area may appear in a standard
C00065 00034	@ And here's the second.
C00066 00035	@ The third.
C00067 00036	@ Conversely, here is a routine that takes three strings and prints a file
C00068 00037	@ Another system-dependent routine is needed to convert three \TeX\ strings
C00071 00038	@ A messier routine is also needed, since format file names must be scanned
C00072 00039	@ @<Set init...@>=
C00073 00040	@ Here is the messy routine that was just mentioned. It sets |name_of_file|
C00077 00041	@ Operating systems often make it possible to determine the exact name (and
C00079 00042	@ Here we have to remember that the |input_ln| routine
C00082 00043	@!dvi_index=0..dvi_buf_size {an index into the output buffer}
C00083 00044	@ Some systems may find it more efficient to make |dvi_buf| a |packed|
C00084 00045	@ The actual output of |dvi_buf[a..b]| to |dvi_file| is performed by calling
C00086 00046	@<Finish the \.{DVI} file@>=
C00089 00047	@ @<Finish issuing a diagnostic message for an overfull or underfull hbox@>=
C00090 00048	@ @<Finish issuing a diagnostic message for an overfull or underfull vbox@>=
C00091 00049	@ Here we do whatever is needed to complete \TeX's job gracefully
C00093 00050	@* \[54] System-dependent changes.
C00098 ENDMK
C⊗;
\def\botofcontents{\vskip 0pt plus 1fil minus 1.5in}
\setcount0 3
\let\maybe=\no
@z
The |banner| string defined here should be changed whenever \TeX\
undergoes any modifications, so that it will be clear which version of
\TeX\ might be the guilty party when a problem arises.
@↑extensions to \TeX@>

@d banner=='This is TeX, WAITS Version 0.95' {printed when \TeX\ starts}
@↑system dependencies@>
@z
@ Some of the code below is intended to be used only when diagnosing the
strange behavior that sometimes occurs when \TeX\ is being installed or
when system wizards are fooling around with \TeX\ without quite knowing
what they are doing. Such code will not normally be compiled; it is
delimited by the codewords `$|debug|\ldots|gubed|$', with apologies
to people who wish to preserve the purity of English. Similarly, there
is some conditional code delimited by `$|stat|\ldots|tats|$'
that is intended only for use when statistics
are to be kept about \TeX's memory usage.

@d debug==@{ {change this to `$\\{debug}\eqv\null$' when debugging}
@d gubed==@t@>@} {change this to `$\\{gubed}\eqv\null$' when debugging}
@f debug==begin
@f gubed==end
@#
@d stat== {change this to `$\\{stat}\eqv\.{@@\{}$' when not
	gathering usage statistics}
@d tats== {change this to `$\\{tats}\eqv\.{@@\}}$' when not
	gathering usage statistics}
@f stat==begin
@f tats==end
@↑system dependencies@>
@z
@ This program has two important variations: (1) There is a long and slow
version called \.{INITEX}, which does the extra calculations need to
@.INITEX@>
initialize \TeX's internal tables; and (2)@@there is a shorter and faster
production version, which cuts the initialization to a bare minimum.
Parts of the program that are needed in (1) but not in (2) are delimited by
the codewords `$|init|\ldots|tini|$'.

(Here we are not really changing \.{TEX.WEB}; this module is included in the
change file so that \.{INITEX.CH} and \.{TEX.CH} have the same basic structure.)

@d init== {change this to `$\\{init}\eqv\.{@@\{}$' in the production version}
@d tini== {change this to `$\\{tini}\eqv\.{@@\}}$' in the production version}
@f init==begin
@f tini==end

@<Initialize whatever...@>=
@<Set initial values of key variables@>@/
init @<Initialize table entries (done by \.{INITEX} only)@>@;@+tini
@↑system dependencies@>
@z
@<Compiler directives@>=
@{@&$C-,A+,D-,W+@}
	{no range check, catch arithmetic overflow, no debug overhead}
debug @{@&$C+,D:5,W+@}@+ gubed {but turn everything on when debugging}
{the `|W+|' switch catches more syntax errors}
{the `\ignorespaces|D:5|' avoids initial stop for the debugger}
@↑system dependencies@>
@z
@ The following parameters can be changed at compile time to extend or
reduce \TeX's capacity. They may have different values in \.{INITEX} and
in production versions of \TeX.
@.INITEX@>
@↑system dependencies@>

@<Constants...@>=
@!mem_max=37000; {greatest index in \TeX's internal |mem| array,
	must be strictly less than |max_halfword|;
	also, for \ph, we must keep total variable storage < 128K words}
@!buf_size=500; {maximum number of characters simultaneously present in
	current lines of open files}
@!error_line=80; {width of context lines on terminal error messages}
@!half_error_line=50; {width of first lines of contexts in terminal
	error messages, should be between 30 and |error_line-15|}
@!max_print_line=80; {width of longest text lines output, should be at least 60}
@!stack_size=80; {maximum number of simultaneous input sources}
@!max_in_open=6; {maximum number of input files and error insertions that
	can be going on simultaneously}
@!font_max=100;
	{maximum internal font number, must not exceed |max_quarterword|}
@!font_mem_size=23000; {number of words of |font_info| for all fonts}
@!param_size=30; {maximum number of simultaneous macro parameters}
@!nest_size=40; {maximum number of semantic levels simultaneously active}
@!max_strings=3000; {maximum number of strings}
@!string_vacancies=8000; {the minimum number of characters that should be
	available for the user's control sequences and font names,
	after \TeX's own error messages are stored}
@!pool_size=31000; {maximum number of characters in strings, including all
	error messages and help texts, and the names of all fonts and
	control sequences; must exceed |string_vacancies| by the total
	length of \TeX's own strings, which is currently about 22000}
@!save_size=300; {space for saving values outside of current group, must be
	at most |max_halfword|}
@!trie_size=8000; {space for hyphenation patterns, should be larger for
	\.{INITEX} than it is in production versions of \TeX}
@!dvi_buf_size=800; {size of the output buffer, must be a multiple of 8}
@!file_name_size=23; {file names shouldn't be longer than this}
@!pool_name='TEX.POOL[TEX,SYS]      ';
	{string of length |file_name_size|, where string pool appears}
@↑system dependencies@>
@z
One can't simply make helter-skelter changes to the following constants,
since certain rather complex initialization
numbers are computed from them. They are defined here using
\.{WEB} macros, instead of being put into \PASCAL's |const| list, in order to
emphasize this distinction.

@d mem_base=0 {smallest index in the |mem| array, must not be less
	than |min_halfword|}
@d hi_mem_base=25000 {smallest index in the single-word area of |mem|,
	must be substantially larger than |mem_base| and smaller than |mem_max|}
@d font_base=0 {smallest internal font number, must not be less
	than |min_quarterword|}
@d hash_size=2100 {maximum number of control sequences; it should be at most
	about |(mem_max-hi_mem_base)/6|, but 2100 is already quite generous}
@d hash_prime=1777 {a prime number equal to about 85\%\ of |hash_size|}
@d hyph_size=307 {another prime; the number of \.{\\hyphenation} exceptions}
@↑system dependencies@>
@z
@ The ascii code is ``standard'' only to a certain extent, since many
computer installations have found it advantageous to have ready access
to more than 94 printing characters. Appendix@@C of the \TeX\ manual
gives a complete specification of the intended correspondence between
characters and \TeX's internal representation.

The code shown here is intended to be used on the Stanford {\sc SAIL} system,
and at other installations like CMU and ISI where essentially the same
extended character set is used. The fact that {\mc SAIL} has |'}'| in the
wrong place turns out to cause no difficulty in this case.

@<Set initial values...@>=
for i←1 to @'37 do xchr[i]←chr(i);
xchr[@'30]←chr(@'137);
xchr[@'32]←chr(@'33); {|not_equal| sign}
xchr[@'33]←chr(@'176);
@↑system dependencies@>
@z
@p function a_open_in(var f:alpha_file):boolean;
	{open a text file for input}
begin reset(f,name_of_file,'/E/O/N:19');
	{the \.{/E} switch distinguishes |form_feed| from |carriage_return|;
	the \.{/O} switch gives error control to us;
	and the \.{/N:19} switch specifies 19 buffers, which is
	supposedly the thing to do at {\mc SAIL}}
a_open_in←not eof(f);
end;
@#
function a_open_out(var f:alpha_file):boolean;
	{open a text file for output}
begin rewrite(f,name_of_file,'/O'); a_open_out←eof(f);
end;
@#
function b_open_in(var f:byte_file):boolean;
	{open a binary file for input}
begin reset(f,name_of_file,'/B:8/O/N:19'); b_open_in←not eof(f);
end;	{the \.{/B} switch is necessary to get byte packing}
@#
function b_open_out(var f:byte_file):boolean;
	{open a binary file for output}
begin rewrite(f,name_of_file,'/O/N:19'); b_open_out←eof(f);
end;	{here we don't pack it since |ary_out| is going to be used}
@#
function w_open_in(var f:word_file):boolean;
	{open a word file for input}
begin reset(f,name_of_file,'/O/N:19'); w_open_in←not eof(f);
end;
@#
function w_open_out(var f:word_file):boolean;
	{open a word file for output}
begin rewrite(f,name_of_file,'/O/N:19'); w_open_out←eof(f);
end;
@↑system dependencies@>
@z
@ Input from text files is read one line at a time, using a routine called
|input_ln|. This function is defined in terms of global variables
called |buffer|, |first|, and |last|
that will be  described in detail later; for now, it suffices for us
to know that |buffer| is an array of |ascii_code| values, and that
|first| and |last| are indices into this array representing the
beginning and ending of a line of text.

We will read the lines first into an auxiliary buffer, in order to
save the running time of procedure-call overhead. This uses a nice
feature of \ph\ that Knuth chose not to mention in \TeX82.
@↑Knuth, Donald Ervin@>

At {\mc SAIL} we want to recognize page marks (indicated by |form_feed|
characters), and keep track of the current page number.

@d form_feed=@'14 {ascii code used at end of a page}

@<Glob...@>=
@!buffer:array[0..buf_size] of ascii_code; {lines of characters being read}
@!first:0..buf_size; {the first unused position in |buffer|}
@!last:0..buf_size; {end of the line just input to |buffer|}
@!max_buf_stack:0..buf_size; {largest index used in |buffer|}
@!aux_buf:array[0..70] of text_char; {where the characters go first}
@↑system dependencies@>
@z
@p function input_ln(var f:alpha_file):boolean; {inputs the next line
	or returns |false|}
label 1;
var n: integer;
@!k,@!m: 0..buf_size; {indices into |buffer|}
begin get(f); {input the first character of the line into |f↑|}
if f↑=chr(@'12) then get(f); {skip past a |line_feed|}
if eof(f) then input_ln←false
else	begin last←first;
	read(f,aux_buf:n);
	if buffer[first]=form_feed then {previous line was end-of-page}
		begin incr(page); line←1; {adjust line and page numbers}
		end;
1:	if last+n>max_buf_stack then
		if last+n≥buf_size then
			begin max_buf_stack←buf_size;
			overflow("buffer size",buf_size);
			end
		else max_buf_stack←last+n;
	if n>0 then
		begin m←last;
		if n=72 then last←m+71@+else last←m+n;
		for k←m to last-1 do buffer[k]←xord[aux_buf[k-m]];
		if n=72 then {there's more on this line}
			begin read(f,aux_buf:n); goto 1;
			end;
		end
	else if f↑=chr(form_feed) then {end of page}
		begin aux_buf[0]←f↑; n←1; goto 1;
		end;
	input_ln←true;
	end;
end;
@↑system dependencies@>
@z
@ The user's terminal acts essentially like other files of text, except
that it is used both for input and for output. When the terminal is
considered an input file, the file variable is called |term_in|, and when it
is considered an output file the file variable is |term_out|.
On WAITS, this point is moot, since we use the built-in |TTY| file.
@↑system dependencies@>

@d term_in==TTY {the terminal as an input file}
@d term_out==TTY {the terminal as an output file}
@z
@ Here is how to open the terminal files
on WAITS: we don't do anything, since |TTY| is always open.
Note that |eoln(term_in)| is initially |true|.
@↑system dependencies@>

@d t_open_in==do_nothing {open the terminal for text input}
@d t_open_out==do_nothing {open the terminal for text output}
@z
@ The following program does the required initialization
and accepts interrupts and also retrieves a possible command line, using
new system routines due to David R. Fuchs.
@↑Fuchs, David Raymond@>

@d pto_chr(#)==ptwr1w(0,ord(#)); {put a character in the line editor}

@p procedure esci(var x:integer); extern; @t\2@>@;
	{increments |x| each time the user types escape-I or break-I;
	the program can change |x| whenever it wants to, but |x| had
	better be a global variable}
@#
function rescan:boolean; extern; @t\2@>@;
	{puts the command line into the terminal buffer,
	or returns |false| if there was no command line}
@#
function tmp_in(f:s@&t@&r@&i@&n@&g;var s:s@&t@&r@&i@&n@&g):integer; extern;
	@t\2@>@;
	{reads \.{TMPCOR} file |f| into |s|, and returns its length
		(|≤0| means error)}
@#
function cclsw: boolean; extern; @t\2@>@;
	{was program started with \.{RUN} offset of 1 (i.e., from \.{SNAIL})?}
@#
procedure ptwr1w(pty,c:integer); extern; @t\2@>@;
	{simulates typing of a character on a \.{PTY}}
@#
function init_terminal:boolean; {gets the terminal files started}
label exit;
var l:integer; {length returned by |tmp_in|}
@!line_found:boolean; {have we scanned a line?}
@!tmp_cor_buf:packed array[0..100] of char; {where |tmp_in| puts things}
begin t_open_in;
esci(interrupt);
last←first;
if cclsw then {started by \.{TEX} monitor command}
	begin l←tmp_in('TEX',tmp_cor_buf);
	loc←1;
	while (loc<l)∧(tmp_cor_buf[loc]≠'←') do incr(loc);
	incr(loc);
	while loc<l do
		begin if tmp_cor_buf[loc]>' ' then
			begin buffer[last]←xord[tmp_cor_buf[loc]]; incr(last);
			end;
		incr(loc);
		end;
	end
else
debug if false then@;@+gubed@;@/
if rescan then
	begin read_ln(term_in); {get first character into |term_in↑|}
	while (¬ eoln(term_in))∧(term_in↑≠';') do get(term_in);
	if term_in↑=';' then
		begin get(term_in);
		while ¬ eoln(term_in) do
			begin buffer[last]←xord[term_in↑]; incr(last); get(term_in);
			end;
		end;
	end;
line_found←(last>first);
loop@+	begin loc←first;
	while (loc<last)∧(buffer[loc]=" ") do incr(loc);
	if loc<last then
		begin init_terminal←true;
		return; {return unless the line was all blank}
		end;
	if line_found then
		write_ln(term_out,'Please type the name of your input file.');
	write(term_out,'**'); update_terminal;
@.**@>
	buffer[first]←0; {|input_ln| may look at |buffer[first]|}
	if not input_ln(term_in) then {this shouldn't happen}
		begin write_ln(term_out);
		write(term_out,'! End of file on the terminal... why?');
@.End of file on the terminal@>
		init_terminal←false; return;
		end;
	line_found←true;
	end;
exit:end;
@↑system dependencies@>
@z
@<Character |k| cannot be printed@>=
	k in [0,@'11..@'15,@'33]
@↑system dependencies@>
@z
@p procedure term_input; {gets a line from the terminal}
var k:0..buf_size; {index into |buffer|}
begin update_terminal; {Now the user sees the prompt for sure}
buffer[first]←0; {makes sure |input_ln| doesn't find a |form_feed|}
if not input_ln(term_in) then fatal_error("End of file on the terminal!");
@.End of file on the terminal@>
term_offset←0; {the user's line ended with carriage return}
decr(selector); {prepare to echo the input}
if last≠first then for k←first to last-1 do print(buffer[k]);
print_ln; incr(selector); {restore previous status}
end;
@↑system dependencies@>
@z
@ It is desirable to provide an `\.E' option here that gives the user
an easy way to return from \TeX\ to the system editor, with the offending
line ready to be edited. The present implementation does this by loading
the line editor with the appropriate call to the editor. We treat `\.T' the
same as `\.E', because other programs on this system invoke the editor
when the user says `\.T'.

There is a secret `\.D' option available when the debugging routines have
not been commented out.
@↑debugging@>

@<Interpret code |c| and |return| if done@>=
case c of
"1","2","3","4","5","6","7","8","9": if deletions_allowed then
	@<Delete |c-"0"| tokens, |goto continue|@>;
@t\4\4@>@;@+debug "D": begin debug_help; goto continue;@+end;@+gubed@/
"E","T": if base_ptr>0 then
	begin selector←new_string; pool_ptr←str_start[str_ptr];
	print("et "); print(input_stack[base_ptr].name_field);
	print_char("/"); print_int(page); print("p/");
	print_int(line); print_char("l");
	if str_ptr<max_strings then
		begin pseudo_typein←str_ptr; incr(str_ptr);
		str_start[str_ptr]←pool_ptr;
		end; {|make_string| not declared |forward|}
	selector←term_and_log; interaction←scroll_mode; jump_out;
	end;
"H": @<Print the help information, |goto continue|@>;
"I":@<Introduce new material from the terminal and |return|@>;
"Q","R","S":@<Change the interaction level and |return|@>;
"X":begin interaction←scroll_mode; jump_out;
	end;
othercases do_nothing
endcases;@/
@<Print the menu of available options@>
@↑system dependencies@>
@z
It is usually most efficient to have |min_quarterword=min_halfword=0|,
so one should try to achieve this unless it causes a severe problem.
The values defined here are recommended for most 36-bit computers.

@d min_quarterword=0 {smallest allowable value in a |quarterword|}
@d max_quarterword=511 {largest allowable value in a |quarterword|}
@d min_halfword==0 {smallest allowable value in a |halfword|}
@d max_halfword==262143 {largest allowable value in a |halfword|}

@↑system dependencies@>
@z
The inner loop of \TeX\ will run faster with respect to compilers
that don't optimize expressions like `|x+0|' and `|x-0|', if these
macros are simplified in the obvious way when |min_quarterword=0|.
So they have been simplified here in the obvious way.
@↑inner loop@>

@d qi(#)==# {to put an |eight_bits| item into a quarterword}
@d qo(#)==# {to take an |eight_bits| item from a quarterword}

@z
cat_code(carriage_return)←car_ret; cat_code(" ")←spacer;
cat_code("\")←escape; cat_code(form_feed)←car_ret;
cat_code(invalid_code)←invalid_char; cat_code(null_code)←ignore;
for k←"A" to "Z" do
	begin cat_code(k)←letter; cat_code(k+"a"-"A")←letter;@/
	math_code(k)←k+var_code; math_code(k+"a"-"A")←k+"a"-"A"+var_code;@/
	lc_code(k)←k+"a"-"A"; lc_code(k+"a"-"A")←k+"a"-"A";@/
	uc_code(k)←k; uc_code(k+"a"-"A")←k;@/
	sf_code(k)←999;
	end;

@z
@ The following procedure, which is called just before \TeX\ initializes its
input and output, establishes the initial values of the date and time.
It uses a {\mc WAITS} monitor call that puts the date in the left 18 bits
and the time in the right 18 bits.

@p procedure fix_date_and_time;
var t:integer; {accumulator}
date:integer; {raw date}
g:boolean; {garbage}
begin calli(@'400101,,t,t,g); {that's \.{ACCTIM}}
date←t div @'1000000;
time←(t mod @'1000000) div 60;
day←(date mod 31)+1;
month←((date div 31) mod 12)+1;
year←(date div (31*12))+1964;
end;
@↑system dependencies@>
@z
The global variable |line| contains the line number in the topmost
open file, for use in error messages. If we are not reading from
the terminal, |line_stack[index]| holds the line number for the
enclosing level, so that |line| can be restored when the current
file has been read.

Similarly, we maintain a global variable |page| and a corresponding
|page_stack|.

@d terminal_input==(name=0) {are we reading from the terminal?}
@d cur_file==input_file[index] {the current |alpha_file| variable}

@<Globals...@>=
@!in_open : 0..max_in_open; {the number of lines in the buffer, less one}
@!input_file : array[1..max_in_open] of alpha_file;
@!line : integer; {current line number in the current source file}
@!line_stack : array[0..max_in_open] of integer;
@!page : integer; {current page number in the current source file}
@!page_stack : array[0..max_in_open] of integer;
@↑system dependencies@>
@z
@<Print location of current line@>=
if name≤16 then
	if terminal_input then
		if base_ptr=0 then print_nl("<*>") else print_nl("<insert> ")
	else	begin print_nl("<read "); print_int(name-1); print_char(">");
		end
else	begin if page>1 then
		begin print_nl("p."); print_int(page); print(",l.");
		end
	else print_nl("l.");
	print_int(line);
	end;
print_char(" ")
@↑system dependencies@>
@z
@ The |begin_file_reading| procedure starts a new level of input for lines
of characters to be read from a file, or as an insertion from the
terminal. It does not take care of opening the file, nor does it set |loc|
or |limit| or |line| or |page|.

@p procedure begin_file_reading;
begin if in_open=max_in_open then overflow("text input levels",max_in_open);
if first=buf_size then overflow("buffer size",buf_size);
incr(in_open); push_input; index←in_open;
line_stack[index]←line; start←first; state←mid_line;
name←0; {|terminal_input| is now |true|}
page_stack[index]←page;
end;
@↑system dependencies@>
@z
@ Conversely, the variables must be downdated when such a level of input
is finished:

@p procedure end_file_reading;
begin first←start; page←page_stack[index]; line←line_stack[index];
if name>16 then a_close(cur_file); {forget it}
pop_input; decr(in_open);
end;
@↑system dependencies@>
@z
@p procedure check_outer_validity;
var p:pointer; {points to inserted token list}
@!q:pointer; {auxiliary pointer}
begin if scanner_status≠normal then
	begin @<Back up an outer control sequence so that it can be reread@>;
	if scanner_status>skipping then
		@<Tell the user what has run away and try to recover@>
	else	begin print_nl("! Incomplete "); print_cmd_chr(if_test,cur_if);
@.Incomplete \\if...@>
		print("; all text was ignored after line "); print_int(skip_line);
		if skip_page>1 then
			begin print(", p."); print_int(skip_page);
			end;
		help3("A forbidden control sequence occurred in skipped text.")@/
		("This kind of error happens when you say `\if...' and forget")@/
		("the matching `\fi'. I've inserted a `\fi'; this might work.");
		if cs_ptr≠0 then cs_ptr←0
		else help_line[2]←@|
			"The file ended while I was skipping conditional text.";
		cur_tok←cs_token_flag+frozen_fi; ins_error;
		end;
	end;
end;
@↑system dependencies@>
@z
@ If the user has set the |pausing| parameter to some nonzero value,
and if nonstop mode has not been selected,
each line of input is displayed in the transcript file, followed by `\.{=>}',
and also put into the user's line-editor buffer.
\TeX\ waits for the line to be edited, and the next line received is
used instead of the line in the file.

@p procedure firm_up_the_line;
var k:0..buf_size; {an index into |buffer|}
begin limit←last;
if (pausing≠0)∧(interaction>nonstop_mode)∧(buffer[start]≠form_feed) then
	begin print_ln;
	if start=limit then {empty line will be made nonempty so that it's visible}
		begin buffer[start]←" "; incr(limit);
		end;
	decr(selector); {inhibit terminal output temporarily}
	for k←start to limit-1 do
		begin print_char(buffer[k]);
		pto_chr(xchr[buffer[k]]);
		end;
	print("=>"); first←start;
	if not input_ln(term_in) then fatal_error("End of file on the terminal!");
@.End of file on the terminal@>
	if last>first then for k←first to last-1 do print_char(buffer[k]);
	limit←last; print_ln; incr(selector);
	end;
end;
@↑system dependencies@>
@z
@ The first line of a file must be treated specially, since |input_ln|
starts with |get|. Furthermore we want to omit the optional file
directory page present at {\mc SAIL}.
@↑system dependencies@>

@<Input the first line of |read_file[n]|@>=
begin if eoln(read_file[n]) then last←start
else	begin buffer[start]←xord[read_file[n]↑]; first←start+1;
	if ¬ input_ln(read_file[n]) then confusion("read");
@:this can't happen read}{\quad read@>
	if(last-start=29)∧(buffer[start]="C")∧(buffer[start+8]=@'26) then
		begin while (read_file[n]↑≠chr(form_feed))∧(not eof(read_file[n])) do
			begin read_ln(read_file[n]); read(read_file[n],aux_buf:temp_ptr);
			end; {skip the directory}
		buffer[start]←form_feed; last←start+1;
		end;
	end;
read_open[n]←normal;
end
@↑system dependencies@>
@z
@ When we skip conditional text, we keep track of the line number
where skipping began, for use in error messages. Also the page number.

@<Glob...@>=
@!skip_line,@!skip_page:integer; {skipping began here}
@↑system dependencies@>
@z
@p procedure pass_text;
label done;
var l:integer;
@!save_scanner_status:small_number; {|scanner_status| upon entry}
begin save_scanner_status←scanner_status; scanner_status←skipping; l←0;
skip_line←line; skip_page←page;
loop@+	begin get_next;
	if cur_cmd=fi_or_else then
		begin if l=0 then goto done;
		if cur_chr=fi_code then l←l-1;
		end
	else if cur_cmd=if_test then l←l+1;
	end;
done: scanner_status←save_scanner_status;
end;
@↑system dependencies@>
@z
@ The file names we shall deal with have the following structure:
If the name contains `\.[', the file area consists of all characters
from this character to the end; otherwise the file area is null.
If the remaining file name contains `\..', the file extension consists of all
such characters from this character to the end, otherwise the file extension
is null. We can assume that there is at most one `\.[' and at most one `\..'.

We can scan such file names easily by using two global variables that keep track
of the occurrences of area and extension delimiters:

@<Glob...@>=
@!area_delimiter:pool_pointer; {the most recent `\.[', if any}
@!ext_delimiter:pool_pointer; {the relevant `\..', if any}
@↑system dependencies@>
@z
@ Input files that can't be found in the user's area may appear in a standard
system area called |TEX_area|. Font files whose areas are not given explicitly
are assumed to appear in a standard system area called |TEX_font_area|.
These system area names will, of course, vary from place to place.

@d TEX_area=="[tex,sys]"
@d TEX_font_area=="[tex,sys]"
@↑system dependencies@>
@z
@ And here's the second.

@p function more_name(c:ascii_code):boolean;
begin if c=" " then more_name←false
else	begin if c="[" then area_delimiter←pool_ptr
	else if c="." then ext_delimiter←pool_ptr;
	str_room(1); append_char(c); {contribute |c| to the current string}
	more_name←true;
	end;
end;
@↑system dependencies@>
@z
@ The third.

@p procedure end_name;
begin if str_ptr+3>max_strings then overflow("number of strings",max_strings);
cur_name←str_ptr;
if ext_delimiter=0 then cur_ext←""
else	begin incr(str_ptr);
	str_start[str_ptr]←ext_delimiter; cur_ext←str_ptr;
	end;
if area_delimiter≤str_start[str_ptr] then
	begin cur_area←""; incr(str_ptr); str_start[str_ptr]←pool_ptr;
	end
else	begin incr(str_ptr);
	str_start[str_ptr]←area_delimiter; cur_area←make_string;
	end;
end;
@↑system dependencies@>
@z
@ Conversely, here is a routine that takes three strings and prints a file
name that might have produced them. (The routine is system dependent, because
some operating systems put the file area last instead of first.)

@<Basic printing...@>=
procedure print_file_name(@!n,@!a,@!e:str_number);
begin print(n); print(e); print(a);
end;
@↑system dependencies@>
@z
@ Another system-dependent routine is needed to convert three \TeX\ strings
into the |name_of_file| value that is used to open files.

A special convention is used here with respect to font metric files: If the
file name is longer than six characters (e.g., `\.{helvetica}' or
`\.{oldenglish}'), we abbreviate it by retaining the first three and
last three characters (e.g., `\.{helica}' or `\.{oldish}').

@d append_to_name(#)==begin c←#; incr(k);
	if (c≥"a")∧(c≤"z") then c←c-@'40; {convert to upper case}
	if k≤file_name_size then name_of_file[k]←xchr[c];
	end

@p procedure pack_file_name(@!n,@!a,@!e:str_number);
var k:integer; {number of positions filled in |name_of_file|}
@!c: ascii_code; {character being packed}
@!j:pool_pointer; {index into |string_pool|}
begin k←0;
if (e=".tfm")∧(length(n)>6) then
	begin for j←str_start[n] to str_start[n]+2 do
		append_to_name(str_pool[j]);
	for j←str_start[n+1]-3 to str_start[n+1]-1 do
		append_to_name(str_pool[j]);
	end
else for j←str_start[n] to str_start[n+1]-1 do append_to_name(str_pool[j]);
for j←str_start[e] to str_start[e+1]-1 do append_to_name(str_pool[j]);
for j←str_start[a] to str_start[a+1]-1 do append_to_name(str_pool[j]);
if k≤file_name_size then name_length←k@+else name_length←file_name_size;
for k←name_length+1 to file_name_size do name_of_file[k]←' ';
end;
@↑system dependencies@>
@z
@ A messier routine is also needed, since format file names must be scanned
before \TeX's string mechanism has been initialized. We shall use the
global variable |TEX_format_default| to supply the text for default system
areas and extensions related to format files.

@d format_default_length=18 {length of the |TEX_format_default| string}
@d format_area_length=9 {length of its area part}
@d format_ext_length=4 {length of its `\.{.fmt}' part}

@<Glob...@>=
@!TEX_format_default:packed array[1..format_default_length] of char;
@↑system dependencies@>
@z
@ @<Set init...@>=
TEX_format_default←'PLAIN.fmt[tex,sys]';
@.PLAIN@>
@↑system dependencies@>
@z
@ Here is the messy routine that was just mentioned. It sets |name_of_file|
from the first |n| characters of |TEX_format_default|, followed by
|buffer[a..b]|, followed by the last |format_ext_length| characters of
|TEX_format_default|; but it actually switches stuff around to keep the
file area last.

We dare not give error messages here, since \TeX\ calls this routine before
the |error| routine is ready to roll. Instead, we simply drop excess characters,
since the error will be detected in another way when a strange file name
isn't found.

@p procedure pack_buffered_name(@!n:small_number;@!a,@!b:integer);
var k:integer; {number of positions filled in |name_of_file|}
@!c: ascii_code; {character being packed}
@!j:integer; {index into |buffer| or |TEX_format_default|}
@!d:integer; {a kludge}
begin if n+b-a+1+format_ext_length>file_name_size then
	b←a+file_name_size-n-1-format_ext_length;
k←0;
for j←a to b do append_to_name(buffer[j]);
if b=0 then
	begin d←format_default_length-format_area_length+1;
	n←format_default_length;
	end
else d←format_default_length-format_area_length-format_ext_length+1;
for j←d to format_default_length-format_area_length do
	append_to_name(xord[TEX_format_default[j]]);
for j←format_default_length-n+1 to format_default_length do
	append_to_name(xord[TEX_format_default[j]]);
if k≤file_name_size then name_length←k@+else name_length←file_name_size;
for k←name_length+1 to file_name_size do name_of_file[k]←' ';
end;
@↑system dependencies@>
@z
@ Operating systems often make it possible to determine the exact name (and
possible version number) of a file that has been opened. The following routine,
which simply makes a \TeX\ string from the value of |name_of_file|, should
ideally be changed to deduce the full name of file@@|f|,
if it is possible to do this in a \PASCAL\ program.

Here is how Dave Fuchs taught \TeX\ to do this on {\mc WAITS}:
@↑Fuchs, David Raymond@>

@p procedure cur_nam(var chan:f@&i@&l@&e;var s:string); extern; @t\2@>@/
function make_name_string(var f:f@&i@&l@&e):str_number;
var s:packed array[1..24] of char;
@!k:1..24; {file names at {\sc SAIL} have at most 23 characters}
begin cur_nam(f,s); str_room(24); k←1;
while ord(s[k])≠0 do
	begin append_char(xord[s[k]]); incr(k);
	end;
make_name_string←make_string;
end;
function a_make_name_string(var f:alpha_file):str_number;
begin a_make_name_string←make_name_string(f);
end;
function b_make_name_string(var f:byte_file):str_number;
begin b_make_name_string←make_name_string(f);
end;
function w_make_name_string(var f:word_file):str_number;
begin w_make_name_string←make_name_string(f);
end;
@↑system dependencies@>
@z
@ Here we have to remember that the |input_ln| routine
starts with a |get|. Also, we want to skip past the editor's directory
page if it is present.

@<Read the first line...@>=
begin if eoln(cur_file) then last←start
else	begin buffer[start]←xord[cur_file↑]; first←start+1;
	if ¬ input_ln(cur_file) then confusion("input");
@:this can't happen input}{\quad input@>
	if(last-start=29)∧(buffer[start]="C")∧(buffer[start+8]=@'26) then
		begin while (cur_file↑≠chr(form_feed))∧(not eof(cur_file)) do
			begin read_ln(cur_file); read(cur_file,aux_buf:temp_ptr);
			end; {skip the directory}
		buffer[start]←form_feed; last←start+1;
		end;
	end;
firm_up_the_line;
buffer[limit]←carriage_return; first←limit+1; loc←start; line←1; page←1;
end
@↑system dependencies@>
@z
@!dvi_index=0..dvi_buf_size; {an index into the output buffer}
@!packed_bytes=packed array[dvi_index] of eight_bits;
	{buffer for \.{DVI} output}
@↑system dependencies@>
@z
@ Some systems may find it more efficient to make |dvi_buf| a |packed|
array, since output of four bytes at once may be facilitated.

@<Glob...@>=
@!dvi_buf:packed_bytes; {buffer for \.{DVI} output}
@!half_buf:dvi_index; {half of |dvi_buf_size|}
@!dvi_limit:dvi_index; {end of the current half buffer}
@!dvi_ptr:dvi_index; {the next available buffer address}
@!dvi_offset:integer; {|dvi_buf_size| times the number of times the
	output buffer has been emptied}
@!dvi_gone:integer; {the number of bytes already output to |dvi_file|}
@↑system dependencies@>
@z
@ The actual output of |dvi_buf[a..b]| to |dvi_file| is performed by calling
|write_dvi(a,b)|. For best results, this procedure should be optimized to
run as fast as possible on each particular system, since it is part of
\TeX's inner loop. It is safe to assume that |a| and |b+1| will both be
multiples of 4 when |write_dvi(a,b)| is called; therefore it is possible on
many machines to use efficient methods to pack four bytes per word and to
output an array of words with one system call.
@↑inner loop@>

@p procedure ary_out(var f:file;@!b:packed_bytes; @!o,@!c:integer);
	extern;@t\2@>@#
procedure write_dvi(@!a,@!b:dvi_index);
begin ary_out(dvi_file,dvi_buf,a div 4,(b+1-a)div 4);
end;
@↑system dependencies@>
@z
@<Finish the \.{DVI} file@>=
if total_pages=0 then print_nl("No output file.")
@.No output file@>
else	begin dvi_out(post); {beginning of the postamble}
	dvi_four(last_bop); last_bop←dvi_offset+dvi_ptr-5; {|post| location}
	dvi_four(25400000); dvi_four(473628672); {conversion ratio for sp}
	prepare_mag; dvi_four(mag); {magnification factor}
	dvi_four(max_v); dvi_four(max_h);@/
	dvi_out(max_push div 256); dvi_out(max_push mod 256);@/
	dvi_out(total_pages div 256); dvi_out(total_pages mod 256);@/
	@<Output the font definitions for all fonts that were used@>;
	dvi_out(post_post); dvi_four(last_bop); dvi_out(id_byte);
	k←4+((dvi_buf_size-dvi_ptr) mod 4); {the number of 223's}
	while k>0 do
		begin dvi_out(223); decr(k);
		end;
	@<Empty the last bytes out of |dvi_buf|@>;
	print_nl("Output written on "); print(output_file_name);
@.Output written on x@>
	print(" ("); print_int(total_pages); print(" page");
	if total_pages≠1 then print_char("s");
	print(", "); print_int(dvi_offset+dvi_ptr); print(" bytes).");
	b_close(dvi_file);
	if pseudo_typein=0 then
		begin k←selector; selector←new_string;
		pool_ptr←str_start[str_ptr];
		print("r DVIdover;"); print(output_file_name); 
		selector←k; str_room(1);
		pseudo_typein←make_string;
		end;
	end
@↑system dependencies@>
@z
@ @<Finish issuing a diagnostic message for an overfull or underfull hbox@>=
if output_active then print(") has occurred while \output is active")
else	begin if par_begin_line≠0 then
		begin print(") in paragraph at lines "); print_int(par_begin_line);
		print("--");
		end
	else print(") detected at line ");
	print_int(line);
	if page>1 then
		begin print(", p."); print_int(page);
		end;
	end;
print_ln;@/
font_in_short_display←null_font; short_display(list_ptr(r)); print_ln;@/
begin_diagnostic; show_box(r); end_diagnostic(true)
@↑system dependencies@>
@z
@ @<Finish issuing a diagnostic message for an overfull or underfull vbox@>=
if output_active then print(") has occurred while \output is active")
else	begin print(") detected at line "); print_int(line);
	if page>1 then
		begin print(", p."); print_int(page);
		end;
	print_ln;@/
	end;
begin_diagnostic; show_box(r); end_diagnostic(true)
@↑system dependencies@>
@z
@ Here we do whatever is needed to complete \TeX's job gracefully
on the local operating system.

The new stuff at {\mc SAIL} has to do with preparing for what the user
presumably wants to do next, by typing it for him/her.

@<Last-minute...@>=

procedure close_files_and_terminate;
var k:integer; {all-purpose index}
begin @<Finish the extensions@>;
stat if tracing_stats≠0 then @<Output statistics about this job@>;@;@+tats@/
@<Finish the \.{DVI} file@>;
if job_name>0 then
	begin write_ln(log_file); a_close(log_file);
	end;
if (pseudo_typein≠0)∧(interaction>batch_mode) then
	begin write_ln(term_out);
	for k←str_start[pseudo_typein] to str_start[pseudo_typein+1]-1 do
		pto_chr(xchr[str_pool[k]]);
	end;
end;
@↑system dependencies@>
@z
@* \[54] System-dependent changes.
Here are the remaining things needed to make the implementation
complete at {\mc SAIL}.
@↑system dependencies@>

@ The |pseudo_typein| variable is set nonzero if the |error| routine
uses the `\.E' option to exit and edit.

@<Glob...@>=
@!pseudo_typein:str_number;

@ @<Set init...@>=
pseudo_typein←0; page←0;
@z